package com.yuw.niodemo;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * 服务器客户端通信流程图：https://www.processon.com/view/link/60af07bb6376893238dffee5
 */
public class NIOClient {
    // 缓存的size
    private static final int BLOCK_SIZE = 4096;
    // 客户端发送信息的缓存区
    private static ByteBuffer sendBuffer = ByteBuffer.allocate(BLOCK_SIZE);
    // 客户端接收信息的缓存区
    private static ByteBuffer receiveBuffer = ByteBuffer.allocate(BLOCK_SIZE);
    // 建立客户端的网络信息对象：ip地址和端口号--- 和服务器的端口号保持一致
    private static final InetSocketAddress SERVER_ADDRESS = new InetSocketAddress("127.0.0.1", 9093);

    public static void main(String[] args) {
        new NIOClient().start();
    }

    /**
     * 客户端进行操作的主方法 --- 客户端进行通信的业务处理
     */
    public void start() {
        try {
            //打开客户端的socket通道
            SocketChannel socketChannel = SocketChannel.open();
            //设置为非阻塞模式
            socketChannel.configureBlocking(false);
            //建立一个客户单端的选择器，并使用open启动选择器
            Selector selector = Selector.open();
            //客户端的channel向 selector 选择器注册此通道
            socketChannel.register(selector, SelectionKey.OP_CONNECT); // 请求的类型 是 connect 请求进行连接
            // 执行向服务器的连接动作
            socketChannel.connect(SERVER_ADDRESS);

            // 声明一个 客户端 channel 临时变量，用于接收selector选择器选出的准备好的连接通道
            SocketChannel client;

            for(;;) {
                //选择一组键
                selector.select();
                //返回此选择器的已选择键集 (连接通道的描述符)
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                // 遍历选择器选择的key集，使用set集合迭代器依次进行处理
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                //遍历对应的 SelectionKey 处理 (连接通道的描述符)
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = (SelectionKey) iterator.next();
                    // 判断此键的 channel 通道是否已完成其套接字连接操作
                    if (selectionKey.isConnectable()) {
                        System.out.println("Client:  already connected.");
                        // 获取当前的channel对象
                        client = (SocketChannel) selectionKey.channel();
                        //判断该通道是否进行连接过程、完成连接过程
                        if (client.isConnectionPending()) {
                            client.finishConnect(); // 完成了连接操作
                            // 清空缓存区，准备发送信息
                            sendBuffer.clear();
                            // 将发送的信息放入缓存区对象
                            sendBuffer.put("hello nio server".getBytes());
                            // 重置一下状态
                            sendBuffer.flip();
                            // 客户端channel使用write 方法消息
                            client.write(sendBuffer); //将数据写入该通道
                            // 重新将该channel 注册到选择器中，并状态由 write 变为 read
                            client.register(selector, SelectionKey.OP_READ);
                        }
                    } else if (selectionKey.isReadable()) {
                        //获取该键中对应的通道
                        client = (SocketChannel) selectionKey.channel();
                        // 清空缓存区
                        receiveBuffer.clear();
                        // 从管道读取内容到缓存区
                        int count = client.read(receiveBuffer);
                        // 是否读取到了内容，则使用读取到的内容进行业务逻辑处理
                        if (count > 0) {
                            // 把缓存区读取到的字节流内容转化为字符串
                            String receiveMessage = new String(receiveBuffer.array(), 0, count);
                            System.out.println("Client：接收到来自Server的消息，" + receiveMessage);
                            // 重新将该 channel 注册到选择器中，并状态由 read 变为 write
                            client.register(selector, SelectionKey.OP_WRITE);
                        }
                    } else if (selectionKey.isWritable()) {
                        // 清空缓存区
                        sendBuffer.clear();
                        // 获取当前的 channel 对象
                        client = (SocketChannel) selectionKey.channel();
                        // 需要发送的信息
                        String sendText = "hello server,key..";
                        // 将发送的信息转换为字节数组
                        sendBuffer.put(sendText.getBytes());
                        //将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
                        sendBuffer.flip();
                        // 发送信息到服务器
                        client.write(sendBuffer);

                        System.out.println("Client:客户端向服务器端发送数据--：" + sendText);
                        // 重新将该channel 注册到选择器中，并状态由 write 变为 read
                        client.register(selector, SelectionKey.OP_READ);
                    }
                }
                // 清空Key集，以便于进行下一次选择处理
                selectionKeys.clear();
                Thread.sleep(3000); // 休眠3s
            }

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}
